MapIncludedInterceptor.prepareData   A
last analyzed

Complexity

Conditions 2

Size

Total Lines 14
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 14
rs 10
c 0
b 0
f 0
cc 2
1
import { ResponseInterceptorInterface } from './response-interceptor';
2
import { AxiosResponse } from 'axios';
3
import { ApiResponseBodyOk, BEditaClientResponse, JsonApiResourceObject } from '../bedita-api-client';
4
5
/**
6
 * Interface for MapIncludedInterceptor configuration.
7
 *
8
 * - replaceWithTranslation: translation language (replace main objects fields with translated fields if not empty)
9
 */
10
export interface MapIncludedConfig {
11
    replaceWithTranslation?: string,
12
}
13
14
/**
15
 * Formatter interceptor for mapping include data inside the related object data
16
 */
17
export class MapIncludedInterceptor implements ResponseInterceptorInterface {
18
19
    /**
20
     * Interceptor configuration
21
     */
22
    #config: MapIncludedConfig;
23
24
    /**
25
     * Constructor.
26
     *
27
     * @param config The configuration for the interceptor.
28
     */
29
    public constructor(config: MapIncludedConfig = {}) {
30
        this.setConfig(config);
31
    }
32
33
    /**
34
     * Set configuration.
35
     *
36
     * @param config The config data.
37
     * @param merge If config should be merged or not
38
     */
39
    public setConfig(config: MapIncludedConfig, merge = true): void {
40
        if (!merge) {
41
            this.#config = config;
42
43
            return;
44
        }
45
46
        this.#config = { ...this.#config, ...config };
47
    }
48
49
    /**
50
     * Get the interceptor configuration.
51
     */
52
    public getConfig(): MapIncludedConfig {
53
        return this.#config;
54
    }
55
56
    /**
57
     * When response has included data they are formatted as
58
     *
59
     * ```
60
     * {
61
     *     "data": {
62
     *          "id": "123",
63
     *          "type": "resource-one",
64
     *          "attributes": {},
65
     *          "relationships": {
66
     *              "rel_one": {
67
     *                  "data": [
68
     *                      {
69
     *                          "id": "234",
70
     *                          "type": "resource-two",
71
     *                          "attributes": {},
72
     *                          ...
73
     *                      },
74
     *                      ...
75
     *                  ],
76
     *              },
77
     *              "rel_two": {},
78
     *              ...
79
     *          }
80
     *      }
81
     * }
82
     * ```
83
     *
84
     * It works also when `data` is an array of resources.
85
     *
86
     * @param response The response.
87
     */
88
    public responseHandler(response: AxiosResponse): Promise<BEditaClientResponse<any>> {
89
        const responseData: ApiResponseBodyOk = response.data;
90
        let { data } = responseData;
91
        const { included = false } = responseData;
92
93
        if (included !== false && included.length > 0) {
94
            data = this.prepareData(data, included);
95
        }
96
97
        const beditaResponse = response as BEditaClientResponse;
98
        beditaResponse.formattedData = {data};
99
100
        return Promise.resolve(beditaResponse);
101
    }
102
103
    /**
104
     * @inheritdoc
105
     */
106
    public errorHandler(error: any): Promise<any> {
107
        return Promise.reject(error);
108
    }
109
110
    /**
111
     * Prepare data mapping the resource inside included array
112
     * in the relative relation inside relationships object.
113
     *
114
     * @param data The starting data
115
     * @param included The included resources
116
     */
117
    protected prepareData(data: JsonApiResourceObject | JsonApiResourceObject[], included: JsonApiResourceObject[]) {
118
        if (!Array.isArray(data)) {
119
            return this.mapIncluded(data, included);
120
        }
121
122
        return data.map(d => this.mapIncluded(d, included));
123
    }
124
125
    /**
126
     * For every relationships it maps the related included resource data in relationships itself.
127
     *
128
     * @param data The Json API resource obejct
129
     * @param included The included data
130
     */
131
    protected mapIncluded(data: JsonApiResourceObject, included: Array<JsonApiResourceObject>): JsonApiResourceObject {
132
        const relationships = data?.relationships || {};
133
134
        Object.keys(relationships).forEach((rel) => {
135
            let d: Array<JsonApiResourceObject> = relationships[rel]?.data || [];
136
            if (d.length > 0) {
137
                d = d.map(dItem => {
138
                    const includedData = included.find(includedItem => includedItem.id === dItem.id);
139
                    if (rel === 'translations' && includedData.attributes?.lang === this.#config?.replaceWithTranslation) {
140
                        data.attributes = { ...data.attributes, ...this.extractTranslatedFields(includedData) };
141
                    }
142
143
                    return includedData;
144
                });
145
            }
146
147
            relationships[rel].data = d;
148
        });
149
150
        data.relationships = relationships;
151
152
        return data;
153
    }
154
155
    /**
156
     * Extract `translated_fields` removing empty values.
157
     *
158
     * @param data The data to analyse
159
     * @returns
160
     */
161
    protected extractTranslatedFields(data: JsonApiResourceObject) {
162
        const translatedFields = { ...data?.attributes?.translated_fields || {} };
163
        Object.keys(translatedFields).forEach(key => {
164
            if (!translatedFields[key] || translatedFields[key] === '') {
165
                delete translatedFields[key];
166
            }
167
        });
168
169
        return translatedFields;
170
    }
171
}
172